// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

// mode(s) for the sequence chart dsl's mscgen, xù and msgenny
// For more information on mscgen, see the site of the original author:
// http://www.mcternan.me.uk/mscgen
//
// This mode for mscgen and the two derivative languages were
// originally made for use in the mscgen_js interpreter
// (https://sverweij.github.io/mscgen_js)

(function(mod) {
  if (typeof exports == 'object' && typeof module == 'object')
    // CommonJS
    mod(require('../../lib/codemirror'));
  else if (typeof define == 'function' && define.amd)
    // AMD
    define(['../../lib/codemirror'], mod);
  // Plain browser env
  else mod(CodeMirror);
})(function(CodeMirror) {
  'use strict';

  var languages = {
    mscgen: {
      keywords: ['msc'],
      options: ['hscale', 'width', 'arcgradient', 'wordwraparcs'],
      constants: ['true', 'false', 'on', 'off'],
      attributes: [
        'label',
        'idurl',
        'id',
        'url',
        'linecolor',
        'linecolour',
        'textcolor',
        'textcolour',
        'textbgcolor',
        'textbgcolour',
        'arclinecolor',
        'arclinecolour',
        'arctextcolor',
        'arctextcolour',
        'arctextbgcolor',
        'arctextbgcolour',
        'arcskip',
      ],
      brackets: ['\\{', '\\}'], // [ and  ] are brackets too, but these get handled in with lists
      arcsWords: ['note', 'abox', 'rbox', 'box'],
      arcsOthers: [
        '\\|\\|\\|',
        '\\.\\.\\.',
        '---',
        '--',
        '<->',
        '==',
        '<<=>>',
        '<=>',
        '\\.\\.',
        '<<>>',
        '::',
        '<:>',
        '->',
        '=>>',
        '=>',
        '>>',
        ':>',
        '<-',
        '<<=',
        '<=',
        '<<',
        '<:',
        'x-',
        '-x',
      ],
      singlecomment: ['//', '#'],
      operators: ['='],
    },
    xu: {
      keywords: ['msc', 'xu'],
      options: ['hscale', 'width', 'arcgradient', 'wordwraparcs', 'watermark'],
      constants: ['true', 'false', 'on', 'off', 'auto'],
      attributes: [
        'label',
        'idurl',
        'id',
        'url',
        'linecolor',
        'linecolour',
        'textcolor',
        'textcolour',
        'textbgcolor',
        'textbgcolour',
        'arclinecolor',
        'arclinecolour',
        'arctextcolor',
        'arctextcolour',
        'arctextbgcolor',
        'arctextbgcolour',
        'arcskip',
      ],
      brackets: ['\\{', '\\}'], // [ and  ] are brackets too, but these get handled in with lists
      arcsWords: [
        'note',
        'abox',
        'rbox',
        'box',
        'alt',
        'else',
        'opt',
        'break',
        'par',
        'seq',
        'strict',
        'neg',
        'critical',
        'ignore',
        'consider',
        'assert',
        'loop',
        'ref',
        'exc',
      ],
      arcsOthers: [
        '\\|\\|\\|',
        '\\.\\.\\.',
        '---',
        '--',
        '<->',
        '==',
        '<<=>>',
        '<=>',
        '\\.\\.',
        '<<>>',
        '::',
        '<:>',
        '->',
        '=>>',
        '=>',
        '>>',
        ':>',
        '<-',
        '<<=',
        '<=',
        '<<',
        '<:',
        'x-',
        '-x',
      ],
      singlecomment: ['//', '#'],
      operators: ['='],
    },
    msgenny: {
      keywords: null,
      options: ['hscale', 'width', 'arcgradient', 'wordwraparcs', 'watermark'],
      constants: ['true', 'false', 'on', 'off', 'auto'],
      attributes: null,
      brackets: ['\\{', '\\}'],
      arcsWords: [
        'note',
        'abox',
        'rbox',
        'box',
        'alt',
        'else',
        'opt',
        'break',
        'par',
        'seq',
        'strict',
        'neg',
        'critical',
        'ignore',
        'consider',
        'assert',
        'loop',
        'ref',
        'exc',
      ],
      arcsOthers: [
        '\\|\\|\\|',
        '\\.\\.\\.',
        '---',
        '--',
        '<->',
        '==',
        '<<=>>',
        '<=>',
        '\\.\\.',
        '<<>>',
        '::',
        '<:>',
        '->',
        '=>>',
        '=>',
        '>>',
        ':>',
        '<-',
        '<<=',
        '<=',
        '<<',
        '<:',
        'x-',
        '-x',
      ],
      singlecomment: ['//', '#'],
      operators: ['='],
    },
  };

  CodeMirror.defineMode('mscgen', function(_, modeConfig) {
    var language = languages[(modeConfig && modeConfig.language) || 'mscgen'];
    return {
      startState: startStateFn,
      copyState: copyStateFn,
      token: produceTokenFunction(language),
      lineComment: '#',
      blockCommentStart: '/*',
      blockCommentEnd: '*/',
    };
  });

  CodeMirror.defineMIME('text/x-mscgen', 'mscgen');
  CodeMirror.defineMIME('text/x-xu', { name: 'mscgen', language: 'xu' });
  CodeMirror.defineMIME('text/x-msgenny', {
    name: 'mscgen',
    language: 'msgenny',
  });

  function wordRegexpBoundary(pWords) {
    return new RegExp('\\b(' + pWords.join('|') + ')\\b', 'i');
  }

  function wordRegexp(pWords) {
    return new RegExp('(' + pWords.join('|') + ')', 'i');
  }

  function startStateFn() {
    return {
      inComment: false,
      inString: false,
      inAttributeList: false,
      inScript: false,
    };
  }

  function copyStateFn(pState) {
    return {
      inComment: pState.inComment,
      inString: pState.inString,
      inAttributeList: pState.inAttributeList,
      inScript: pState.inScript,
    };
  }

  function produceTokenFunction(pConfig) {
    return function(pStream, pState) {
      if (pStream.match(wordRegexp(pConfig.brackets), true, true)) {
        return 'bracket';
      }
      /* comments */
      if (!pState.inComment) {
        if (pStream.match(/\/\*[^\*\/]*/, true, true)) {
          pState.inComment = true;
          return 'comment';
        }
        if (pStream.match(wordRegexp(pConfig.singlecomment), true, true)) {
          pStream.skipToEnd();
          return 'comment';
        }
      }
      if (pState.inComment) {
        if (pStream.match(/[^\*\/]*\*\//, true, true)) pState.inComment = false;
        else pStream.skipToEnd();
        return 'comment';
      }
      /* strings */
      if (!pState.inString && pStream.match(/\"(\\\"|[^\"])*/, true, true)) {
        pState.inString = true;
        return 'string';
      }
      if (pState.inString) {
        if (pStream.match(/[^\"]*\"/, true, true)) pState.inString = false;
        else pStream.skipToEnd();
        return 'string';
      }
      /* keywords & operators */
      if (
        !!pConfig.keywords &&
        pStream.match(wordRegexpBoundary(pConfig.keywords), true, true)
      )
        return 'keyword';

      if (pStream.match(wordRegexpBoundary(pConfig.options), true, true))
        return 'keyword';

      if (pStream.match(wordRegexpBoundary(pConfig.arcsWords), true, true))
        return 'keyword';

      if (pStream.match(wordRegexp(pConfig.arcsOthers), true, true))
        return 'keyword';

      if (
        !!pConfig.operators &&
        pStream.match(wordRegexp(pConfig.operators), true, true)
      )
        return 'operator';

      if (
        !!pConfig.constants &&
        pStream.match(wordRegexp(pConfig.constants), true, true)
      )
        return 'variable';

      /* attribute lists */
      if (
        !pConfig.inAttributeList &&
        !!pConfig.attributes &&
        pStream.match(/\[/, true, true)
      ) {
        pConfig.inAttributeList = true;
        return 'bracket';
      }
      if (pConfig.inAttributeList) {
        if (
          pConfig.attributes !== null &&
          pStream.match(wordRegexpBoundary(pConfig.attributes), true, true)
        ) {
          return 'attribute';
        }
        if (pStream.match(/]/, true, true)) {
          pConfig.inAttributeList = false;
          return 'bracket';
        }
      }

      pStream.next();
      return 'base';
    };
  }
});
